package client.jobs;

import client.*;
import client.anticheat.CheatTracker;
import client.anticheat.CheatingOffense;
import client.inventory.Item;
import client.inventory.MapleInventoryType;
import client.inventory.ModifyInventory;
import client.jobs.adventurer.ThiefHandler;
import client.jobs.job.AttackType;
import client.jobs.job.MapleJob;
import client.jobs.job.MapleJobCode;
import client.skills.Skill;
import client.skills.SkillFactory;
import client.status.MonsterStatus;
import client.status.MonsterStatusEffect;
import configs.ServerConfig;
import constants.GameConstants;
import constants.JobConstants;
import constants.ServerConstants;
import constants.SkillConstants;
import constants.skills.*;
import handling.channel.handler.AttackInfo;
import handling.opcode.SendPacketOpcode;
import handling.world.WorldBroadcastService;
import handling.world.party.MaplePartyCharacter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import server.*;
import server.life.Element;
import server.life.MapleMonster;
import server.life.MapleMonsterStats;
import server.maps.MapleMap;
import server.maps.MapleMist;
import server.maps.pvp.MaplePvp;
import tools.*;
import tools.data.input.LittleEndianAccessor;
import tools.data.output.MaplePacketLittleEndianWriter;
import tools.packet.ForcePacket;
import tools.packet.InventoryPacket;

import java.awt.*;
import java.util.List;
import java.util.*;
import java.util.Map.Entry;

/**
 *
 * 解析技能抽象类
 *
 */
public abstract class JobHandler implements IJobFunction {

    private static final Logger log = LogManager.getLogger(JobHandler.class.getName());

    /**
     * 获取对应的职业
     * @param chr
     * @return
     */
    public static JobHandler getJobHandler(MapleCharacter chr) {
        short job = chr.getJob();
        if (JobConstants.is冒险家飞侠(job)) {
            return new ThiefHandler();
        }
        return null;

    }


    /**
     *
     *
     * @param slea
     * @param c
     * @param chr
     * @param energy 是否被攻击
     */
    public void closeRangeAttack(LittleEndianAccessor slea, MapleClient c, MapleCharacter chr, boolean energy) {

        // todo 子类实现解析
        AttackInfo attack = parseCloseRangeAttack(slea, chr, energy);

        boolean checkAttackResult = JobCheckUtil.checkAttack(chr, c, attack, AttackType.CLOSE_RANGE);
        if (!checkAttackResult) {
            return;
        }

        double maxdamage = chr.getStat().getCurrentMaxBaseDamage();

        // 初始化信息
        int attackCount = initAttackCount(chr);
        int skillLevel = 0;
        MapleStatEffect effect = null;
        Skill skill = null;

        if (attack.skillId != 0) {
            CheckSkillEffectInfo checkResult = JobCheckUtil.checkSkillAndEffect(attack, AttackType.CLOSE_RANGE, chr, c, energy);
            if (!checkResult.isCheckResult()) {
                return;
            }
            skillLevel = checkResult.getSkillLevel();
            effect = checkResult.getEffect();
            skill = checkResult.getSkill();

            if (chr.checkSoulWeapon() && attack.skillId == chr.getEquippedSoulSkill()) {
                chr.checkSoulState(true);
            }

//            attackCount = effect.getAttackCount(chr);
            // 特殊可以子类获取攻击次数
//            attackCount = getSkillAttackCount(effect, chr, attack);

        }

        // todo 实现是否显示暴击
        attack = Modify_AttackCrit(attack, chr, 1, effect);

        //控制斗气
        maxdamage = handleEnergy(chr, attack, energy, maxdamage);

        chr.checkFollow();
        if (!SkillFactory.isBlockedSkill(attack.skillId)) {
            if (attack.skillId != 新手.升级特效) {
                if (!chr.isHidden()) {
                    //实现包装一个伤害的封包
                    chr.getMap().broadcastMessage(chr, closeRangeAttack2Broadcast(chr, skillLevel, 0, attack, energy), chr.getTruePosition());
                } else {
                    chr.getMap().broadcastGMMessage(chr, closeRangeAttack2Broadcast(chr, skillLevel, 0, attack, energy), false);
                }
            }
        }
        // 子类实现攻击结果
        applyAttack(attack, skill, c.getPlayer(), attackCount, maxdamage, effect, 0);
    }


    /**
     * 初始化数量
     * @param applyfrom
     * @return
     */
    public int initAttackCount(MapleCharacter applyfrom) {
        int initCount = 1;
        if (JobConstants.is双刀(applyfrom.getJob())) {
            // 是否装备了双刀的副刀，普通攻击次数加倍
            Item shield = applyfrom.getInventory(MapleInventoryType.EQUIPPED).getItem((short) -10);
            int attackCount = (shield != null && shield.getItemId() / 10000 == 134 ? 2 : 1);
            initCount = attackCount;
        }
        return initCount;
    }

    protected double handleEnergy(MapleCharacter chr, AttackInfo attack, boolean energy, double maxdamage) {
        if (!energy) {
            // 处理斗气减少设置
            if (JobConstants.is英雄(chr.getJob())) {
                int numFinisherOrbs = 0;
                Integer comboBuff = chr.getBuffedValue(MapleBuffStat.斗气集中);
                if (SkillConstants.isFinisher(attack.skillId) > 0) {
                    if (comboBuff != null) {
                        numFinisherOrbs = comboBuff - 1;
                    }
                    if (numFinisherOrbs > 0) {
                        chr.handleOrbconsume(SkillConstants.isFinisher(attack.skillId));
                        maxdamage *= numFinisherOrbs;
                    }
                }
            }
        }
        return maxdamage;
    }






    /**
     * @param attack              攻击信息
     * @param theSkill            技能信息
     * @param player              角色信息
     * @param attackCount         攻击次数
     * @param effect              技能效果
     * @param maxDamagePerMonster 每个怪物的最大伤害
     * @param visProjectile       可见的子弹、箭矢、飞镖等...
     */
    public void applyAttack(AttackInfo attack, Skill theSkill, MapleCharacter player, int attackCount, double maxDamagePerMonster, MapleStatEffect effect, int visProjectile) {

        boolean attackUserResult = JobCheckUtil.checkApplyAttackUser(player, attack, theSkill);
        if (!attackUserResult) {
            return;
        }
        int mobCount = 0;
        //当攻击技能不等于空
        if (attack.skillId != 0) {

            boolean result = JobCheckUtil.checkSkillInSpecialSpace(player, attack, effect);
            if (!result) {
                return;
            }
            // todo 子类单独控制技能相关的 增加攻击怪物的数量这里只用来检测
            mobCount = applySkillMobCount(player, attack, effect);
            if (mobCount == -1) {
                return;
            }
            player.setLastAttackSkillId(attack.skillId);
        }

        if (player.isShowPacket()) {
            player.dropMessage(-1, "攻击动作: " + Integer.toHexString(attack.display));
        }

        /* 根据角色当前拥有的状态增加攻击次数 */
        //子类单独控制和前面那里可以合并
        //todo 子类具体实现
        attackCount = getSkillAttackCount(effect, player, attack);

        boolean attackCountResult = JobCheckUtil.checkAttackCount(player, attack, effect, mobCount, attackCount);
        if (!attackCountResult) {
            return;
        }


        /* 检测角色主武器是否为灵魂武器，且当前的攻击技能是否为灵魂技能。*/
        if (player.checkSoulWeapon() && attack.skillId == player.getEquippedSoulSkill()) {
            player.checkSoulState(true);
        }

        /* PVP 设置*/
        if (player.getMap().isPvpMap()) {
            MaplePvp.doPvP(player, player.getMap(), attack, effect);
        } else if (player.getMap().isPartyPvpMap()) {
            MaplePvp.doPartyPvP(player, player.getMap(), attack, effect);
        } else if (player.getMap().isGuildPvpMap()) {
            MaplePvp.doGuildPvP(player, player.getMap(), attack, effect);
        }

        /* 定义伤害数据信息 */
        int fixeddmg;                                                   // 固定伤害
        long totalDamage = 0;                                           // 总伤害
        long totDamageToOneMonster = 0;                                 // 总伤害到一个怪物
        long monsterMaxHp = 0;                                          // 怪物最大HP
        long maxDamagePerHit = player.getMaxDamageOver(attack.skillId); // 每次命中怪物时最大的伤害等于角色当前攻击技能的最大伤害值
        MapleMonster monster;                                           // 怪物
        MapleMonsterStats monsterstats;                                 // 怪物状态信息
        int lastKillMob = 0;                                            // 最后一个杀死的怪物
        int lastKillMobExp = 0;                                         // 最后一个杀死的怪物的经验
        Map<Integer, Integer> killMobList = new LinkedHashMap<>();      // 创建一个杀死怪物的列表

        /* 是否为暴击伤害 */
        boolean isCritDamage = false;

        /* 开始解析伤害信息 */
        for (AttackPair oned : attack.allDamage) {
            monster = player.getMap().getMonsterByOid(oned.objectid);
            if (monster != null && monster.getLinkCID() <= 0) {
                totDamageToOneMonster = 0;
                monsterMaxHp = monster.getMobMaxHp();
                monsterstats = monster.getStats();
                fixeddmg = monsterstats.getFixedDamage();
                long eachd;
                for (Pair<Long, Boolean> eachde : oned.attack) {
                    if (eachde.right) {
                        isCritDamage = true;
                    }
                    eachd = eachde.left;
                    if (player.isShowPacket() && eachd > 0) {
                        player.dropMessage(-1, "物理攻击打怪伤害 : " + eachd + " 服务端预计伤害 : " + maxDamagePerHit + " 是否超过 : " + (eachd > maxDamagePerHit) + " 是否爆击: " + eachde.right);
                    }
                    if (fixeddmg != -1) {
                        if (monsterstats.getOnlyNoramlAttack()) { //如果怪物是只容许普通攻击的类型
                            eachd = attack.skillId != 0 ? 0 : fixeddmg;
                        } else {
                            eachd = fixeddmg;
                        }
                    } else {
                        if (monsterstats.getOnlyNoramlAttack()) {
                            eachd = attack.skillId != 0 ? 0 : Math.min(eachd, maxDamagePerHit);  // 转换为服务端来计算伤害数据
                        } else if (!player.isGM()) {
                            if (eachd > maxDamagePerHit && maxDamagePerHit > 2) {
                                player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE, "[伤害: " + eachd + ", 预计伤害: " + maxDamagePerHit + ", 怪物ID: " + monster.getId() + "] [职业: " + player.getJob() + ", 等级: " + player.getLevel() + ", 技能: " + attack.skillId + "]");
                                if (attack.real) {
                                    player.getCheatTracker().checkSameDamage(eachd, maxDamagePerHit);
                                }
                                if (eachd > maxDamagePerHit * 2) {
                                    String banReason = player.getName() + " 被系统封号.[异常攻击伤害值: " + eachd + ", 预计伤害: " + maxDamagePerHit + ", 怪物ID: " + monster.getId() + "] [职业: " + player.getJob() + ", 等级: " + player.getLevel() + ", 技能: " + attack.skillId + "]";
                                    if (player.getLevel() < 10 && eachd >= 10000) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (player.getLevel() < 20 && eachd >= 20000) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (player.getLevel() < 30 && eachd >= 40000) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (player.getLevel() < 50 && eachd >= 60000) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE_2, "[伤害: " + eachd + ", 预计伤害: " + maxDamagePerHit + ", 怪物ID: " + monster.getId() + "] [职业: " + player.getJob() + ", 等级: " + player.getLevel() + ", 技能: " + attack.skillId + "]");
                                }
                            } else {
                                if (eachd > maxDamagePerHit) {
                                    eachd = (int) (maxDamagePerHit);
                                }
                            }
                        }
                    }
                    totDamageToOneMonster += eachd;
                    //force the miss even if they dont miss. popular wz edit
                    if ((eachd == 0 || monster.getId() == 9700021) && player.getPyramidSubway() != null) { //miss
                        player.getPyramidSubway().onMiss(player);
                    }
                }

                /* 总伤害等于总伤害加上对每个怪物的总伤害！*/
                totalDamage += totDamageToOneMonster;

                /* 检测当前怪物是否为自动攻击， 控制器为空 */
                player.checkMonsterAggro(monster);

                /* 处理技能 */

                /* 对怪物的伤害大于0时处理的数据 */
                if (totDamageToOneMonster > 0 || specialSkillHandleDamage(attack)) {

                    /* 处理技能伤害 添加精准伤害被动伤害*/
                    for (Triple<Integer, String, Integer> psdSkill : player.getStat().getPsdSkills()) {
                        if (psdSkill.left == attack.skillId && attack.skillId != 0 && !psdSkill.mid.isEmpty()) {
                            totDamageToOneMonster *= (1.0 + (MapleStatEffectFactory.parseEval(psdSkill.mid, player.getSkillLevel(psdSkill.right)) / 100.0));
                        }
                    }

                    /* todo 特殊攻击效果 */
                    long specialDamage = beforeAttack(player, attack, monster, visProjectile);

                    boolean killmob;
                    if (specialDamage == 0) {
                        killmob = monster.damage(player, totDamageToOneMonster, true, attack.skillId);
                    } else {
                        killmob = monster.damage(player, specialDamage, true, attack.skillId);
                    }

                    if (killmob) {
                        killMobList.put(monster.getObjectId(), monster.getMobExpFromChannel());
                        lastKillMob = monster.getObjectId();
                        lastKillMobExp = monster.getMobExpFromChannel();
                        afterKillMonster(player, monster);
                    }

                    // todo 怪物反射伤害 宙斯盾系统是无视反射的、等技能
                    handleReflectDamage(player, attack, monster);


                    /* 主武器 */
                    Item weapon_ = player.getInventory(MapleInventoryType.EQUIPPED).getItem((byte) -11);
                    if (weapon_ != null) {
                        MonsterStatus stat = GameConstants.getStatFromWeapon(weapon_.getItemId()); //根据主武器的不同，分两种状态：1恐慌 2速度
                        if (stat != null && Randomizer.nextInt(100) < GameConstants.getStatChance()) {
                            MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(stat, GameConstants.getXForStat(stat), GameConstants.getSkillForStat(stat), null, false, 0);
                            monster.applyStatus(player, monsterStatusEffect, false, 10000, false, null);
                        }
                    }


                    /* 当前攻击出的伤害对怪物伤害大于0的话，就根据角色穿戴的武器、技能状态、BUFF状态、给予怪物状态 */
                    if (totDamageToOneMonster > 0) {
                        // todo 处理状态。
                        onAttack(player, monster, attack.skillId, totalDamage, oned);

                    }

                    // 添加状态
                    if (effect != null && effect.getMonsterStati().size() > 0) {
                        if (effect.makeChanceResult()) {
                            for (Map.Entry<MonsterStatus, Integer> z : effect.getMonsterStati().entrySet()) {
                                monster.applyStatus(player, new MonsterStatusEffect(z.getKey(), z.getValue(), theSkill.getId(), null, false, 0), effect.isPoison(), effect.getDuration(), true, effect);
                            }
                        }
                    }
                }
            }
        }

        //攻击怪物时的一些特殊处理 统一所有怪物都得上的
        if (monsterMaxHp > 0 && totDamageToOneMonster > 0) {
            // todo 处理事后状态
            afterAttack(player, attack.numAttacked, attack.numDamage, attack.skillId, isCritDamage);
            if (!killMobList.isEmpty() && lastKillMob > 0) {
                handleKillMobs(player, killMobList.size(), lastKillMob, lastKillMobExp);
            }
        }

        //特殊技能效果处理
        if (effect != null && attack.skillId != 0 && attack.numAttacked > 0 && !SkillConstants.isNoDelaySkill(attack.skillId) && !SkillConstants.isNoApplyTo(attack.skillId)) {
            boolean isApplyTo = true;
            if (effect.is超越攻击() && totDamageToOneMonster <= 0) {
                isApplyTo = false;
            }

            // 所有有的都要经过
            if (isApplyTo) {
                if (player.isShowPacket()) {
                    player.dropDebugMessage(1, "[攻击BUFF] 技能: " + SkillFactory.getSkillName(attack.skillId) + "(" + attack.skillId + ")");
                }
                effect.applyTo(player, attack.position, false); //这个地方是处理减少角色使用技能的HP或者MP
            }
        }


        //检测角色是否使用无敌
        if (totalDamage > 1 && SkillConstants.getAttackDelay(attack.skillId, theSkill) >= 100) {
            CheatTracker tracker = player.getCheatTracker();
            tracker.setAttacksWithoutHit(true);
            if (tracker.getAttacksWithoutHit() >= 50) {
                tracker.registerOffense(CheatingOffense.ATTACK_WITHOUT_GETTING_HIT, "无敌自动封号.");
            }
        }
    }





    // 特殊技能控制
    protected boolean specialSkillHandleDamage(AttackInfo attack) {
        switch (attack.skillId) {
            case 圣骑士.圣域:
                return true;
        }
        return false;
    }

    // 控制反伤无效
    protected void handleReflectDamage(MapleCharacter player, AttackInfo attack, MapleMonster monster) {
        boolean reflectDamage = true;
        if (attack.skillId == 尖兵.宙斯盾系统 || player.getBuffedValue(MapleBuffStat.至圣领域) != null || player.hasBuffSkill(狂龙战士.终极变形_超级)) {
            reflectDamage = false;
        }
//      if (player.isShowPacket()) {
//          player.dropDebugMessage(1, "[技能信息] 怪物是否反射伤害: " + reflectDamage);
//      }
        if (reflectDamage && monster.isBuffed(MonsterStatus.MOB_STAT_PCounter)) {
            player.addHP(-(7000 + Randomizer.nextInt(8000)));
        }
    }





    /*
     * 爆击伤害
     * type 1近战 2远程 3魔法
     *
     */
    public AttackInfo Modify_AttackCrit(AttackInfo attack, MapleCharacter chr, int type, MapleStatEffect effect) {
        int criticalRate = chr.getStat().passive_sharpeye_rate() + (effect == null ? 0 : effect.getCritical());


        if (attack.skillId == 侠盗.金钱炸弹) {
            return attack;
        }

        int critStorage = chr.getBuffedIntValue(MapleBuffStat.暴击蓄能);
        if (critStorage > 0) {
            criticalRate += critStorage;
            if (!attack.allDamage.isEmpty()) {
                chr.cancelEffectFromBuffStat(MapleBuffStat.暴击蓄能);
            }
        }
        boolean shadow = chr.getBuffedValue(MapleBuffStat.影分身) != null && (type == 1 || type == 2);

        List<Long> damages = new ArrayList<Long>();
        List<Long> damage = new ArrayList<Long>();
        int hit, toCrit, mid_att;
        for (AttackPair pair : attack.allDamage) {
            if (pair.attack != null) {
                hit = 0;
                mid_att = shadow ? (pair.attack.size() / 2) : pair.attack.size();
                toCrit = attack.skillId == 侠盗.暗杀_1 || attack.skillId == 箭神.一击要害箭 || attack.skillId == 双弩.闪电刀刃 || attack.skillId == 双刀.暗影飞跃斩 || attack.skillId == 双刀.地狱锁链 || attack.skillId == 战神.巨熊咆哮 ? mid_att : 0;
                if (toCrit == 0) {
                    for (Pair<Long, Boolean> eachd : pair.attack) {
                        if (!eachd.right && hit < mid_att) {
                            if (eachd.left > 999999 || Randomizer.nextInt(100) < criticalRate) {
                                toCrit++;
                            }
                            damage.add(eachd.left);
                        }
                        hit++;
                    }
                    if (toCrit == 0) {
                        damage.clear();
                        continue;
                    }
                    Collections.sort(damage);
                    for (int i = damage.size(); i > damage.size() - toCrit; i--) {
                        damages.add(damage.get(i - 1));
                    }
                    damage.clear();
                }
                hit = 0;
                // 是否显示暴击 damages放暴击的伤害
                for (Pair<Long, Boolean> eachd : pair.attack) {
                    if (!eachd.right) {
                        if (attack.skillId == 侠盗.暗杀) {
                            eachd.right = hit == 3;
                        } else if (attack.skillId == 箭神.一击要害箭 || attack.skillId == 双弩.闪电刀刃 || attack.skillId == 战神.巨熊咆哮 || attack.skillId == 双刀.暗影飞跃斩 || attack.skillId == 双刀.地狱锁链 || eachd.left > 999999) { //snipe always crit
                            eachd.right = true;
                        } else if (hit >= mid_att) {
                            eachd.right = pair.attack.get(hit - mid_att).right;
                        } else {
                            eachd.right = damages.contains(eachd.left);
                        }
                    }
                    hit++;
                }
                damages.clear();
            }
        }
        return attack;
    }


    /**
     * 解析近距离攻击封包
     *
     * @param lea
     * @param chr
     * @param energy
     * @return
     */
    public AttackInfo parseCloseRangeAttack(LittleEndianAccessor lea, MapleCharacter chr, boolean energy) {
        // 攻击信息
        AttackInfo ai = new AttackInfo();
        ai.isCloseRangeAttack = true; //设置该攻击为近距离攻击
        lea.skip(1); //00
        ai.numAttackedAndDamage = lea.readByte();
        ai.numAttacked = (byte) ((ai.numAttackedAndDamage >>> 4) & 0xF); //攻击怪物数
        ai.numDamage = (byte) (ai.numAttackedAndDamage & 0xF); //攻击次数
        ai.skillId = lea.readInt(); //技能ID
        ai.skllv = lea.readByte();

        Skill skill = SkillFactory.getSkill(ai.skillId);

        // todo 跳1
        parseCloseJobSkip1(ai, lea, energy);

        lea.skip(1);
        ai.cashSlot = lea.readShort();
        lea.skip(4); // crc

        // todo 解析按压技能？
        parseCloseAICharge(chr, ai, lea, energy, skill);
        // todo 解析跳2
        parseCloseJobSkip2(chr, ai, lea);


        lea.skip(6);
        ai.unk = lea.readByte();
        ai.display = lea.readByte(); //动作
        ai.direction = lea.readByte(); //方向
//        if (chr.getCygnusBless()) { //精灵的祝福
//            lea.skip(12); //3个相同的Int
//        }
        lea.skip(4); // big bang
        lea.skip(1); // Weapon class
        ai.speed = lea.readByte(); //攻击速度
        ai.lastAttackTickCount = lea.readInt(); // Ticks

        // todo 跳3
        parseCloseJobSkip3(chr, ai, lea, energy, skill);

        // 包装伤害
        ai.allDamage = new ArrayList<>();
        int oid;
        long damage;
        List<Pair<Long, Boolean>> allDamageNumbers;
        long maxDamagePerHit = chr.getMaxDamageOver(ai.skillId);
        boolean isOutput = false;
        for (int i = 0; i < ai.numAttacked; i++) {
            oid = lea.readInt(); // 怪物编号
            ai.ef = lea.readByte();
            lea.skip(19); //V.112修改 以前19
            allDamageNumbers = new ArrayList<>();
            for (int j = 0; j < ai.numDamage; j++) {
                damage = lea.readLong(); //打怪的伤害
                if (damage > maxDamagePerHit * 1.5 || damage < 0 || oid <= 0) {
                    if (chr.isShowPacket()) {
                        chr.dropDebugMessage(2, "[近距离攻击] 打怪数量: " + ai.numAttacked + " 打怪次数: " + ai.numDamage + " 怪物ID " + oid + " 伤害: " + damage + " 默认上限: " + maxDamagePerHit);
                    }
                    if (ServerConstants.isShowGMMessage()) {
                        WorldBroadcastService.getInstance().broadcastGMMessage(MaplePacketCreator.serverNotice(6, "[GM消息] " + chr.getName() + " ID: " + chr.getId() + " (等级 " + chr.getLevel() + ") 近距离攻击伤害异常。打怪伤害: " + damage + " 地图ID: " + chr.getMapId()));
                    }
                    if (!isOutput) {
                        isOutput = true;
                        log.error("近距离攻击出错封包: 打怪数量: " + ai.numAttacked + " 打怪次数: " + ai.numDamage + " 怪物ID " + oid + " 伤害: " + damage + " 技能ID: " + ai.skillId + " 默认上限: " + maxDamagePerHit + lea.toString(true));
                    }
                }
                allDamageNumbers.add(new Pair<>(damage, false));
            }
//            chr.dropMessage(6, "暴击次数：" + critNum);
            lea.skip(18);
            ai.allDamage.add(new AttackPair(oid, allDamageNumbers));
        }
        if (lea.available() >= 4) {
            ai.position = lea.readPos();
        }
        return ai;
    }


    /*
     * 解析金钱炸弹攻击
     */
    public static AttackInfo parseMesoExplosion(LittleEndianAccessor lea, AttackInfo ret, MapleCharacter chr) {
        //System.out.println(lea.toString(true));
        byte bullets;
        if (ret.numDamage == 0) { //金钱炸弹攻击怪物为0
            lea.skip(4); //角色坐标
            bullets = lea.readByte();
            for (int j = 0; j < bullets; j++) {
                int mesoid = lea.readInt();
                lea.skip(2); //00 00
                if (chr.isShowPacket()) {
                    chr.dropDebugMessage(1, "[技能信息] 金钱炸弹攻击怪物: 无怪 " + ret.numDamage + " 金币ID: " + mesoid);
                }
                ret.allDamage.add(new AttackPair(mesoid, null));
            }
            lea.skip(2); // 63 02 [00] 0个怪物
            return ret;
        }
        int oid;
        List<Pair<Long, Boolean>> allDamageNumbers;
        for (int i = 0; i < ret.numAttacked; i++) { //金钱炸弹攻击怪物大于0
            oid = lea.readInt();
            lea.skip(19); //以前是16
            bullets = lea.readByte();
            allDamageNumbers = new ArrayList<>();
            for (int j = 0; j < bullets; j++) {
                long damage = lea.readInt();
                if (chr.isShowPacket()) {
                    chr.dropDebugMessage(1, "[技能信息] 金钱炸弹攻击怪物: " + ret.numAttacked + " 攻击次数: " + bullets + " 打怪伤害: " + damage);
                }
                allDamageNumbers.add(new Pair<>(damage, false)); //m.e. never crits
            }
            ret.allDamage.add(new AttackPair(oid, allDamageNumbers));
            lea.skip(8);
        }
        lea.skip(4); //角色坐标
        bullets = lea.readByte();
        for (int j = 0; j < bullets; j++) {
            int mesoid = lea.readInt();
            lea.skip(2); //01 00
            if (chr.isShowPacket()) {
                chr.dropDebugMessage(1, "[技能信息] 金钱炸弹攻击怪物: 有怪 " + bullets + " 金币ID: " + mesoid);
            }
            ret.allDamage.add(new AttackPair(mesoid, null));
        }
        //lea.skip(2);  // 8F 02/ 63 02
        return ret;
    }

    /*
     * 解析炎术士魔法攻击
     */
    public static AttackInfo parseWarLockMagicDamage(LittleEndianAccessor lea, MapleCharacter chr) {
        AttackInfo ret = new AttackInfo();
        ret.isMagicAttack = true; //设置该攻击为魔法攻击
        lea.skip(13);
        ret.numAttackedAndDamage = lea.readByte();
        ret.numAttacked = (byte) ((ret.numAttackedAndDamage >>> 4) & 0xF);
        ret.numDamage = (byte) (ret.numAttackedAndDamage & 0xF);
        ret.skillId = lea.readInt(); //技能ID
        lea.skip(13);
        if (SkillConstants.isMagicChargeSkill(ret.skillId)) {
            ret.charge = lea.readInt();
        } else {
            ret.charge = -1;
        }
        lea.skip(6); // T071新增 未知
        ret.unk = lea.readByte();
        ret.display = lea.readByte(); //动作
        ret.direction = lea.readByte(); //方向
        lea.skip(4); // big bang
        lea.skip(1); // Weapon class
//        if (chr.getCygnusBless()) { //精灵的祝福
//            lea.skip(12); //3个相同的Int
//        }
        ret.speed = lea.readByte(); //攻击速度
        ret.lastAttackTickCount = lea.readInt(); // Ticks
        lea.skip(8); //0
        int oid;
        long damage;
        List<Pair<Long, Boolean>> allDamageNumbers;
        ret.allDamage = new ArrayList<>();
        for (int i = 0; i < ret.numAttacked; i++) {
            oid = lea.readInt(); //怪物ID
            lea.skip(20); //V.112修改 以前19
            allDamageNumbers = new ArrayList<>();
            for (int j = 0; j < ret.numDamage; j++) {
                damage = lea.readLong(); //打怪伤害
                allDamageNumbers.add(new Pair<>(damage, false));
            }
            lea.skip(10);
            ret.allDamage.add(new AttackPair(oid, allDamageNumbers));
        }
        ret.position = lea.readPos();
        return ret;
    }



    /**
     * 处理攻击怪物触发
     */
    public void afterAttack(MapleCharacter player, int mobCount, int attackCount, int skillid, boolean isCritDamage) {
        final PlayerStats stats = player.getStat();
        final PlayerSpecialStats specialStats = player.getSpecialStat();
        int job = player.getJob();
        /*HP恢复机率*/
        if (stats.hpRecoverProp > 0 && Randomizer.nextInt(100) <= stats.hpRecoverProp) {
            if (stats.hpRecover > 0) {
                player.healHP(stats.hpRecover);
            }
            /*HP恢复百分比*/
            if (stats.hpRecover_Percent > 0) {
                /*角色增加HP*/
                player.addHP((int) ((double) stats.getCurrentMaxHp() * (double) stats.hpRecover_Percent / 100.0) * mobCount);
            }
        }
        /*MP恢复机率*/
        if (stats.mpRecoverProp > 0 && !JobConstants.isNotMpJob(job) && Randomizer.nextInt(100) <= stats.mpRecoverProp) {
            if (stats.mpRecover > 0) {
                player.healMP(stats.mpRecover);
            }
        }

        // 特殊状态控制
        afterAttackHandler(player, mobCount, skillid, isCritDamage);
    }



    /**
     * 处理角色连续击杀怪物
     */
    public static void handleKillMobs(MapleCharacter player, int kill, int moboid, int mobexp) {
        int mobKills = player.getMobKills();
        long lastMobKillTime = player.getLastMobKillTime();
        //初始化连续击杀
        if (lastMobKillTime <= 0 || lastMobKillTime + 10000 < System.currentTimeMillis()) {
            mobKills = 0;
        }
        //开始处理连续击杀
        int totalexp;
        if (kill >= 3) {
            totalexp = (int) ((mobexp / 100.0) * kill);
            totalexp = totalexp < 1 ? 1 : totalexp;
            player.gainExp(totalexp, false, false, false);
            player.getClient().announce(MaplePacketCreator.showContinuityKill(true, totalexp, kill, moboid));
        }
        mobKills += kill;
        if (mobKills > 1) {
            totalexp = (int) ((mobexp / 1000.0) * (mobKills >= 300 ? 300 : mobKills));
            totalexp = totalexp < 1 ? 1 : totalexp;
            player.gainExp(totalexp, false, false, false);
            player.getClient().announce(MaplePacketCreator.showContinuityKill(false, totalexp, mobKills, moboid));
        }
        lastMobKillTime = System.currentTimeMillis();
        player.setMobKills(mobKills);
        player.setLastMobKillTime(lastMobKillTime);
    }

    public static void afterKillMonster(MapleCharacter player, MapleMonster monster) {
        if (JobConstants.is剑豪(player.getJob())) {
            player.getSpecialStat().gainJianQi(player.hasBuffSkill(剑豪.拔刀姿势) ? 2 : 1);
            player.getClient().announce(MaplePacketCreator.updateJianQi(player.getSpecialStat().getJianQi()));
        } else if (JobConstants.is林之灵(player.getJob())) {
            if (player.hasBuffSkill(林之灵.嗨_兄弟)) {
                Optional.ofNullable(SkillFactory.getSkill(林之灵.嗨_兄弟)).ifPresent(skill -> {
                    MapleStatEffect effect = skill.getEffect(player.getTotalSkillLevel(林之灵.嗨_兄弟));
                    if (effect.makeChanceResult()) {
                        effect.applySummonEffect(player, false, monster.getTruePosition(), effect.getX(), 0);
                    }
                });
            }
        } else if (JobConstants.is火毒(player.getJob())) {
//            if (player.hasBuffSkill(火毒.燎原之火)) {
//                MapleStatEffect effect = SkillFactory.getSkill(火毒.燎原之火_MIST).getEffect(player.getTotalSkillLevel(火毒.燎原之火));
//                Point point = monster.getPosition();
//                point.y += 50;
//                Rectangle bounds = effect.calculateBoundingBox(point, false);
//                player.getClient().announce(BuffPacket.showFirewall(player.getMap().getSpawnedMistOnMap(), 火毒.燎原之火_MIST, bounds, 0x16));
//            }
        }
    }

    /**
     * 发送广播消息
     */
    public byte[] closeRangeAttack2Broadcast(MapleCharacter chr, int skilllevel, int itemId, AttackInfo attackInfo, boolean energy) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();

        boolean hasMoonBuff = chr.getBuffedIntValue(MapleBuffStat.月光转换) == 1;
        mplew.writeShort(energy ? SendPacketOpcode.ENERGY_ATTACK.getValue() : SendPacketOpcode.CLOSE_RANGE_ATTACK.getValue());

        addAttackBody(mplew, chr, skilllevel, itemId, attackInfo, hasMoonBuff, false);

        return mplew.getPacket();
    }

    public byte[] rangedAttack2Broadcast(MapleCharacter chr, int skilllevel, int itemId, AttackInfo attackInfo) {
        if (ServerConfig.DEBUG_MODE) {
            log.trace("调用");
        }
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();

        mplew.writeShort(SendPacketOpcode.RANGED_ATTACK.getValue());
        addAttackBody(mplew, chr, skilllevel, itemId, attackInfo, false, true);
        if (JobConstants.is神之子(chr.getJob()) && attackInfo.skillId >= 100000000) {
            mplew.writeInt(attackInfo.position.x);
            mplew.writeInt(attackInfo.position.y);
        } else if (attackInfo.skillposition != null) {
            if (attackInfo.skillId == 风灵使者.季候风) {    // 季候风为全屏技能，不需要坐标信息
                mplew.writeLong(0);
            } else {
                mplew.writePos(attackInfo.skillposition); // 有些技能要发送技能的坐标信息
            }
        } else if (attackInfo.skillId == 林之灵.编队攻击) {
            mplew.writeInt(0);
        }

        return mplew.getPacket();
    }

    public byte[] magicAttack(MapleCharacter chr, int skilllevel, int itemId, AttackInfo attackInfo) {
        MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();

        mplew.writeShort(SendPacketOpcode.MAGIC_ATTACK.getValue());
        addAttackBody(mplew, chr, skilllevel, itemId, attackInfo, false, false);

        return mplew.getPacket();
    }




    /**
     * 包装信息  目前还不知道这个发送广播消息的目的是什么
     * @param mplew
     * @param chr
     * @param skilllevel
     * @param itemId
     * @param ai
     * @param hasMoonBuff
     * @param b3  是否远程？
     */
    public void addAttackBody(MaplePacketLittleEndianWriter mplew, MapleCharacter chr, int skilllevel, int itemId, AttackInfo ai, boolean hasMoonBuff, boolean b3) {
        int skillId = ai.skillId;
        mplew.writeInt(chr.getId()); //角色的ID
        mplew.write(0); //V.116.1新增 未知 好像箭矢炮盘 这个地方是1
        mplew.write(ai.numAttackedAndDamage);
        mplew.write(chr.getLevel()); //角色的等级
        mplew.write(skilllevel > 0 && skillId > 0 ? skilllevel : 0);
        if (skilllevel > 0 && skillId > 0) {
            mplew.writeInt(skillId);
        }
        if (JobConstants.is神之子(chr.getJob()) && skillId >= 100000000) {
            mplew.write(0); //这个地方未知 当为1的时候 后面还写个什么
        }
        if (b3 && nw(skillId)) {
            mplew.write(0);
        }
        if (skillId == 阴阳师.紫扇仰波第三击 || skillId == 阴阳师.紫扇仰波 || skillId > 阴阳师.UNKNOW_42001004 && skillId <= 阴阳师.新紫扇仰波第3击) {
            mplew.write(skillId);
        }
        if (hasMoonBuff) {
            mplew.write(0);
            mplew.write(0x02); //攻击怪物的次数加倍
            mplew.writeInt(魂骑士.月光洒落); //月光洒落 技能ID
            mplew.writeShort(20); //技能等级
        }
        mplew.write(b3 ? 8 : ny(skillId));
        mplew.write(0);
        mplew.writeInt(0);
        mplew.writeInt(0);
        mplew.write(ai.display); //攻击的动作效果
        mplew.write(ai.direction); //攻击的方向
        mplew.write(-1); //攻击的速度
        mplew.writeShort(0);
        mplew.writeShort(0);
        mplew.write(0);
        mplew.write(0);
        mplew.write(ai.speed);
        mplew.write(chr.getStat().passive_mastery());
        mplew.writeInt(itemId);
        for (AttackPair oned : ai.allDamage) {
            if (oned.attack != null) {
                mplew.writeInt(oned.objectid);
                mplew.write(ai.ef);
                mplew.writeInt(0); //这个地方好像是颜色相关 但神之子切换状态的时候 这个地方为1 打出的攻击显示颜色和普通的不同
                if (skillId == 侠盗.金钱炸弹 || skillId == 阴阳师.朱玉的咒印 || skillId == 阴阳师.朱玉的咒印_2) {
                    mplew.write(oned.attack.size());
                }
                for (Pair<Long, Boolean> eachd : oned.attack) {
                    if (eachd.right) {
                        mplew.writeLong(eachd.left + Long.MIN_VALUE);
                    } else {
                        mplew.writeLong(eachd.left);
                    }
                }
                if (oned.ksPsychicObjectId >= 0) {
                    mplew.writeInt(oned.ksPsychicObjectId);
                }
                if (skillId == 爆破手.浮空冲压) {
                    mplew.write(0);
                }
            }
        }

        switch (skillId) {
            case 主教.创世之破:
            case 冰雷.闪电矛:
            case 魂骑士.冥河破:
            case 炎术士.龙奴:
                mplew.writeInt(ai.charge);
        }

        switch (skillId) {
            // 为什么要写两次？
            case 主教.创世之破:
            case 冰雷.闪电矛:
            case 魂骑士.冥河破:
            case 炎术士.龙奴:
                mplew.writeInt(ai.charge);
                break;
            case 夜行者.暗影之矛:
            case 地图轮.雷鸣诱饵:
            case 地图轮.解放雷之符文:
            case 神之子.固态身体:
            case 神之子.影子突击_剑气:
            case 侠盗.潜影杀机:
            case 爆莉萌天使.超级诺巴:
            case 地图轮.破灭符文解放:
            case 神之子.影子雨印:
            case 战神.摩诃领域:
            case 风灵使者.季候风:
            case 夜行者.黑暗领地:
            case 奇袭者.海神降临:
                mplew.writeInt(chr.getOldPosition().x);
                mplew.writeInt(chr.getOldPosition().y);
                break;
            case 风灵使者.风霜雪舞:
            case 林之灵.旋风飞行:
            case 超能力者.心魂龙卷风_1:
            case 超能力者.心魂龙卷风_2:
            case 超能力者.心魂龙卷风_3:
                mplew.writeShort(0);
                mplew.writeShort(0);
                break;
            case 米哈尔.圣光十字:
                mplew.write(0);
                break;
            case 林之灵.编队攻击:
                mplew.writeInt(0);
                break;
            case 阴阳师.粉身碎骨:
                mplew.writeShort(0);
                mplew.write(0);
                break;
            case 战神.加速终端_瞄准猎物:
            case 爆破手.神圣狂暴打击:
            case 魂骑士.日月斩:
            case 侠盗.暗影抨击:
            case 侠盗.暗影抨击_1:
            case 侠盗.暗影抨击_2:
            case 侠盗.暗影抨击_3:
                mplew.writeInt(0);
                mplew.writeInt(0);
                break;
            default:
                if (nv(skillId)) {
                    mplew.writeInt(0);
                    mplew.write(0);
                }
        }
    }


    public static boolean nv(int skillId) {
        switch (skillId) {
            case 黑骑士.黑暗之矛:
            case 超能力者.心魂龙卷风_4:
            case 超能力者.心魂龙卷风_5:
            case 超能力者.心魂龙卷风_6:
            case 隐士.多向飞镖_三连环光击破:
            case 隐士.多向飞镖_四连镖:
            case 冲锋队长.超人变形_1:
            case 神炮王.宇宙无敌火炮弹:
                return true;
        }
        return false;
    }

    public static boolean nw(int n2) {
        switch (n2) {
            case 英雄.终极打击_爆击:
            case 英雄.终极打击:
            case 圣骑士.连环环破:
            case 圣骑士.圣域:
            case 火毒.美杜莎之眼:
            case 冰雷.链环闪电:
            case 神射手.骤雨箭矢:
            case 神射手.暴风箭雨:
            case 箭神.穿透箭_四转:
            case 隐士.四连镖:
            case 侠盗.一出双击:
            case 双刀.血雨腥风:
            case 双刀.幽灵一击:
            case 冲锋队长.激怒拳:
            case 冲锋队长.能量爆炸:
            case 冲锋队长.双重爆炸:
            case 冲锋队长.暴怒拳:
            case 船长.爆头:
            case 神炮王.加农火箭炮:
            case 神炮王.集中炮击:
            case 5721007:
            case 龙的传人.真气爆炸:
            case 魂骑士.新月斩:
            case 魂骑士.烈日之刺:
            case 炎术士.轨道烈焰II_LINK:
            case 炎术士.轨道烈焰III_LINK:
            case 炎术士.轨道烈焰IV_LINK:
            case 炎术士.灭绝之焰_LINK:
            case 风灵使者.旋风箭:
            case 夜行者.五倍投掷_最后一击:
            case 奇袭者.疾风:
            case 奇袭者.台风:
            case 奇袭者.霹雳:
            case 战神.巨熊咆哮_2:
            case 战神.巨熊咆哮:
            case 21120006:
            case 战神.比昂德:
            case 龙神.雷之迅捷_攻击:
            case 隐月.鬼斩:
            case 恶魔猎手.恶魔呼吸:
            case 恶魔猎手.恶魔冲击波:
            case 32111003:
            case 机械师.集中射击_IRON_B:
            case 爆破手.重锤出击_2:
            case 爆破手.震波打击_1:
            case 剑豪.神速无双:
            case 剑豪.瞬杀:
            case 剑豪.瞬杀_2:
            case 阴阳师.破邪连击符:
            case 米哈尔.灵魂抨击:
            case 米哈尔.闪耀爆炸:
            case 林之灵.致命三角:
            case 林之灵.编队轰炸:
            case 林之灵.伙伴发射:
            case 林之灵.伙伴发射2:
            case 林之灵.伙伴发射3:
            case 林之灵.伙伴发射4:
            case 林之灵.编队攻击_额外目标: {
                return true;
            }
        }
        return false;
    }

    public static int ny(int n2) {
        switch (n2) {
            case 火毒.火焰灵气:
            case 恶魔猎手.黑暗变形:
            case 阴阳师.结界_破魔:
            case 爆莉萌天使.超级诺巴: {
                return 4;
            }
        }
        return 0;
    }

    public void magicDamage(LittleEndianAccessor slea, final MapleClient c, final MapleCharacter chr) {

        AttackInfo attack = parseMagicDamage(slea, chr);

        boolean checkAttackResult = JobCheckUtil.checkAttack(chr, c, attack, AttackType.MAGIC);
        if (!checkAttackResult) {
            return;
        }
        CheckSkillEffectInfo checkResult = JobCheckUtil.checkSkillAndEffect(attack, AttackType.MAGIC, chr, c, false);
        if (!checkResult.isCheckResult()) {
            return;
        }
        int skillLevel = checkResult.getSkillLevel();
        MapleStatEffect effect = checkResult.getEffect();
        Skill skill = checkResult.getSkill();


        attack = Modify_AttackCrit(attack, chr, 3, effect);
        double maxdamage = chr.getStat().getCurrentMaxBaseDamage() * (effect.getDamage() + chr.getStat().getDamageIncrease(attack.skillId)) / 100.0;
        if (SkillConstants.isPyramidSkill(attack.skillId)) {
            maxdamage = 1;
        } else if (JobConstants.is新手职业(skill.getId() / 10000) && skill.getId() % 10000 == 1000) {
            maxdamage = 40;
        }
        chr.checkFollow();
        if (!SkillFactory.isBlockedSkill(attack.skillId)) {
            if (!chr.isHidden()) {
                chr.getMap().broadcastMessage(chr, magicAttack(chr, skillLevel, 0, attack), chr.getTruePosition());
            } else {
                chr.getMap().broadcastGMMessage(chr, magicAttack(chr, skillLevel, 0, attack), false);
            }
        }
        applyAttackMagic(attack, skill, c.getPlayer(), effect, maxdamage);
    }

    public void applyAttackMagic(AttackInfo attack, Skill theSkill, MapleCharacter player, MapleStatEffect effect, double maxDamagePerMonster) {
        boolean attackUserResult = JobCheckUtil.checkApplyAttackUser(player, attack, theSkill);
        if (!attackUserResult) {
            return;
        }

        player.setLastAttackSkillId(attack.skillId);
        int mobCount = effect.getMobCount(player); //攻击怪物数量

        // todo 子类实现
        int attackCount = getSkillAttackCount(effect, player, attack);//攻击怪物次数

        boolean checkAttackCountResult = JobCheckUtil.checkMagicAttackCount(player, attack, mobCount, attackCount);
        if (!checkAttackCountResult) {
            return;
        }


        boolean result = JobCheckUtil.checkSkillInSpecialSpace(player, attack, effect);
        if (!result) {
            return;
        }

        if (player.isAdmin()) {
            player.dropMessage(-1, "攻击动作: " + Integer.toHexString(attack.display));
        }
        MapleMap map = player.getMap();
        //PVP设置
        if (attack.skillId != 2301002) {
            if (map.isPvpMap()) {
                MaplePvp.doPvP(player, map, attack, effect);
            } else if (map.isPartyPvpMap()) {
                MaplePvp.doPartyPvP(player, map, attack, effect);
            } else if (map.isGuildPvpMap()) {
                MaplePvp.doGuildPvP(player, map, attack, effect);
            }
        }

        /* 定义伤害数据信息 */
        long maxDamagePerHit = player.getMaxDamageOver(attack.skillId); // 每次命中怪物时最大的伤害等于角色当前攻击技能的最大伤害值
        long totDamageToOneMonster = 0;                                 // 总伤害到一个怪物
        int fixeddmg;                                                   // 固定伤害
        long totDamage = 0;                                             // 总伤害
        MapleMonsterStats monsterstats;                                 // 怪物状态信息
        int lastKillMob = 0;                                            // 最后一个杀死的怪物
        int lastKillMobExp = 0;                                         // 最后一个杀死的怪物的经验
        Map<Integer, Integer> killMobList = new LinkedHashMap<>();      // 创建一个杀死怪物的列表
        // 魔力吸收
        Skill eaterSkill = SkillFactory.getSkill(SkillConstants.getMPEaterForJob(player.getJob()));
        int eaterLevel = player.getTotalSkillLevel(eaterSkill);

        //攻击怪物伤害处理
        for (AttackPair oned : attack.allDamage) {
            MapleMonster monster = map.getMonsterByOid(oned.objectid);
            if (monster != null && monster.getLinkCID() <= 0) {
                totDamageToOneMonster = 0;
                monsterstats = monster.getStats();
                fixeddmg = monsterstats.getFixedDamage();
                long eachd;
                for (Pair<Long, Boolean> eachde : oned.attack) {
                    eachd = eachde.left;
                    if (fixeddmg != -1) {
                        eachd = monsterstats.getOnlyNoramlAttack() ? 0 : fixeddmg; // Magic is always not a normal attack
                    } else {
                        if (player.isShowPacket() && eachd > 0) {
                            player.dropMessage(-1, "魔法攻击打怪伤害 : " + eachd + " 服务端预计伤害 : " + maxDamagePerHit + " 是否超过 : " + (eachd > maxDamagePerHit));
                        }
                        if (monsterstats.getOnlyNoramlAttack()) {
                            eachd = 0; // Magic is always not a normal attack
                        } else if (!player.isGM()) {
                            if (eachd > maxDamagePerHit) {
                                player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE_MAGIC, "[伤害: " + eachd + ", 预期: " + maxDamagePerHit + ", 怪物ID: " + monster.getId() + "] [职业: " + player.getJob() + ", 等级: " + player.getLevel() + ", 技能: " + attack.skillId + "]");
                                if (attack.real) { //检测是否为相同的伤害
                                    player.getCheatTracker().checkSameDamage(eachd, maxDamagePerHit);
                                }
                                if (eachd > maxDamagePerHit * 2 && attack.skillId != 夜光.绝对死亡) {
                                    String banReason = player.getName() + " 被系统封号.[异常攻击伤害值: " + eachd + ", 预计伤害: " + maxDamagePerHit + ", 怪物ID: " + monster.getId() + "] [职业: " + player.getJob() + ", 等级: " + player.getLevel() + ", 技能: " + attack.skillId + "]";
                                    if (player.getLevel() < 10 && eachd >= 10000) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (player.getLevel() < 20 && eachd >= 20000) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (player.getLevel() < 30 && eachd >= 40000) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (player.getLevel() < 50 && eachd >= 60000) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (player.getLevel() < 70 && eachd >= 399999) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (player.getLevel() < 150 && eachd >= 599999) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    if (eachd > maxDamagePerHit * 3) {
                                        AutobanManager.getInstance().autoban(player.getClient(), banReason);
                                        return;
                                    }
                                    player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE_MAGIC_2, "[伤害: " + eachd + ", 预期: " + maxDamagePerHit + ", 怪物ID: " + monster.getId() + "] [职业: " + player.getJob() + ", 等级: " + player.getLevel() + ", 技能: " + attack.skillId + "]");
                                }
                            } else {
                                if (eachd > maxDamagePerHit) {
                                    eachd = (int) (maxDamagePerHit);
                                }
                            }
                        }
                    }
                    totDamageToOneMonster += eachd;
                }
                totDamage += totDamageToOneMonster;
                player.checkMonsterAggro(monster);

                if (SkillConstants.getAttackDelay(attack.skillId, theSkill) >= 50 &&
                        !SkillConstants.isNoDelaySkill(attack.skillId) &&
                        !SkillConstants.is不检测范围(attack.skillId) && !monster.getStats().isBoss() &&
                        player.getTruePosition().distanceSq(monster.getTruePosition()) > GameConstants.getAttackRange(effect, player.getStat().defRange)) {
                    if (player.getMapId() != 703002000) {
                        if (ServerConstants.isShowGMMessage()) {
                            WorldBroadcastService.getInstance().broadcastGMMessage(MaplePacketCreator.serverNotice(6, "[GM消息] " + player.getName() + " ID: " + player.getId() + " (等级 " + player.getLevel() + ") 攻击范围异常。职业: " + player.getJob() + " 技能: " + attack.skillId + " [范围: " + player.getTruePosition().distanceSq(monster.getTruePosition()) + " 预期: " + GameConstants.getAttackRange(effect, player.getStat().defRange) + "]"));
                        }
                        player.getCheatTracker().registerOffense(CheatingOffense.ATTACK_FARAWAY_MONSTER, "[范围: " + player.getTruePosition().distanceSq(monster.getTruePosition()) + ", 预期范围: " + GameConstants.getAttackRange(effect, player.getStat().defRange) + " ] [职业: " + player.getJob() + " 技能: " + attack.skillId + " ]"); // , Double.toString(Math.sqrt(distance))
                    }
                }


                // 检测群体治愈能不能吸死？
                if (attack.skillId == 主教.群体治愈 && !monsterstats.getUndead()) {
                    player.getCheatTracker().registerOffense(CheatingOffense.HEAL_ATTACKING_UNDEAD);
                    return;
                }

                if (totDamageToOneMonster > 0) {
                    boolean killmob = monster.damage(player, totDamageToOneMonster, true, attack.skillId);
                    if (killmob) {
                        killMobList.put(monster.getObjectId(), monster.getMobExpFromChannel());
                        lastKillMob = monster.getObjectId();
                        lastKillMobExp = monster.getMobExpFromChannel();
                        afterKillMonster(player, monster);
                    }

                    // todo 怪物反射伤害 宙斯盾系统是无视反射的、等技能
                    handleReflectDamage(player, attack, monster);

                    // todo 下面这些和物理一样，也需要合并onattack整理
                    onAttack(player, monster, attack.skillId, totDamage, oned);


                    //魔力吸收效果处理
                    if (eaterLevel > 0) {
                        eaterSkill.getEffect(eaterLevel).applyPassive(player, monster);
                    }

                } else {

                }

                //给怪物BUFF状态效果
                if (effect != null && effect.getMonsterStati().size() > 0) {
                    if (effect.makeChanceResult()) {
                        for (Entry<MonsterStatus, Integer> z : effect.getMonsterStati().entrySet()) {
                            monster.applyStatus(player, new MonsterStatusEffect(z.getKey(), z.getValue(), theSkill.getId(), null, false, 0), effect.isPoison(), effect.getDuration(), true, effect);
                        }
                    }
                }
            }
        }

        // 这是啥意思？
        if (attack.skillId != 主教.群体治愈) {
            effect.applyTo(player);
        }

        //todo 攻击怪物时的一些特殊处理
        if (totDamage > 1) {
            afterAttack(player, attack.numAttacked, attack.numDamage, attack.skillId, false);
        }
        if (!killMobList.isEmpty() && lastKillMob > 0) {
            handleKillMobs(player, killMobList.size(), lastKillMob, lastKillMobExp);
        }

        if (totDamage > 1 && SkillConstants.getAttackDelay(attack.skillId, theSkill) >= 100) {
            CheatTracker tracker = player.getCheatTracker();
            tracker.setAttacksWithoutHit(true);
            if (tracker.getAttacksWithoutHit() >= 50) {
                tracker.registerOffense(CheatingOffense.ATTACK_WITHOUT_GETTING_HIT, "无敌自动封号.");
            }
        }

        //特殊技能效果处理放在afterAttack里面
        //applyAttackMagicHandler(attack, player);

    }

    /**
     * 特殊处理
     * @param attack
     * @param player
     */
    protected void applyAttackMagicHandler(AttackInfo attack, MapleCharacter player) {
        // 消耗魔量26，最多以80%的伤害攻击6个敌人7次，或者使包括自己在内的队员恢复最大血量的10%。
        if (attack.skillId == 主教.光芒飞箭 && player.getParty() != null) {
            for (MaplePartyCharacter pc : player.getParty().getMembers()) {
                if (pc != null && pc.getMapid() == player.getMapId() && pc.getChannel() == player.getClient().getChannel()) {
                    MapleCharacter other = player.getClient().getChannelServer().getPlayerStorage().getCharacterByName(pc.getName());
                    if (other != null) {
                        other.addHP((int) (player.getStat().getCurrentMaxHp() * SkillFactory.getSkill(主教.光芒飞箭).getEffect(player.getSkillLevel(主教.光芒飞箭)).getX() / 100.0D * 10));
                    }
                }
            }
        }
        //消耗MP500，以1000%的伤害最多对15个敌人造成8次攻击。同时向周围队员发放无视一次HP变成0的增益。
        //冷却时间90秒
        //获得增益的队员在600秒内不会再次获得天堂之门的增益。
        if (attack.skillId == 主教.天堂之门) {
            MapleStatEffect tmpEffect = SkillFactory.getSkill(主教.天堂之门).getEffect(player.getSkillLevel(主教.天堂之门));
            tmpEffect.applyTo(player);
        }
    }

    /*
     * 解析魔法攻击
     */
    public AttackInfo parseMagicDamage(LittleEndianAccessor lea, MapleCharacter chr) {
        AttackInfo ai = new AttackInfo();
        ai.isMagicAttack = true; //设置该攻击为魔法攻击
        lea.skip(1);
        ai.numAttackedAndDamage = lea.readByte();
        ai.numAttacked = (byte) ((ai.numAttackedAndDamage >>> 4) & 0xF);
        ai.numDamage = (byte) (ai.numAttackedAndDamage & 0xF);
        ai.skillId = lea.readInt(); //技能ID
        ai.skllv = lea.readByte();
        lea.skip(16);
        ai.charge = SkillConstants.isMagicChargeSkill(ai.skillId) ? lea.readInt() : -1;
        lea.skip(1);

        parseMagicSkip1(ai, lea);

        ai.unk = lea.readByte();
        ai.display = lea.readByte(); //动作
        ai.direction = lea.readByte(); //方向
        lea.skip(4); // big bang
        lea.skip(1); // Weapon class

        parseMagicSkip2(ai, lea);


        ai.speed = lea.readByte(); //攻击速度
        ai.lastAttackTickCount = lea.readInt(); // Ticks
        lea.skip(4); //0

        parseMagicSkip3(ai, lea);


        int oid;
        long damage;
        List<Pair<Long, Boolean>> allDamageNumbers;
        ai.allDamage = new ArrayList<>();
        boolean isOutput = false;
        long maxDamagePerHit = chr.getMaxDamageOver(ai.skillId);
        for (int i = 0; i < ai.numAttacked; i++) {
            oid = lea.readInt(); //怪物ID
            ai.ef = lea.readByte();
            byte by4 = lea.readByte();
            byte by5 = lea.readByte();
            short s2 = lea.readShort();
            lea.skip(4);
            lea.skip(1);
            lea.skip(4);
            lea.skip(4);
            if (ai.skillId == 阴阳师.朱玉的咒印) {
                lea.skip(1);
                ai.numDamage = lea.readByte();
            } else {
                if (lea.readByte() > 0) {
                    lea.skip(1);
                }
                lea.skip(1);
            }
            allDamageNumbers = new ArrayList<>();
            for (int j = 0; j < ai.numDamage; j++) {
                damage = lea.readLong(); //打怪伤害
                if (chr.isShowPacket()) {
                    chr.dropMessage(-5, "魔法攻击 - 打怪数量: " + ai.numAttacked + " 打怪次数: " + ai.numDamage + " 怪物ID " + oid + " 伤害: " + damage);
                }
                if (damage > maxDamagePerHit * 1.5 || damage < 0 || oid <= 0) {
                    if (chr.isAdmin()) {
                        chr.dropMessage(-5, "魔法攻击出错次数: 打怪数量: " + ai.numAttacked + " 打怪次数: " + ai.numDamage + " 怪物ID " + oid + " 伤害: " + damage + " 默认上限: " + maxDamagePerHit);
                    }
                    if (ServerConstants.isShowGMMessage()) {
                        WorldBroadcastService.getInstance().broadcastGMMessage(MaplePacketCreator.serverNotice(6, "[GM消息] " + chr.getName() + " ID: " + chr.getId() + " (等级 " + chr.getLevel() + ") 魔法攻击伤害异常。打怪伤害: " + damage + " 地图ID: " + chr.getMapId()));
                    }
                    if (!isOutput) {
                        isOutput = true;
                        log.error("魔法攻击出错封包:  打怪数量: " + ai.numAttacked + " 打怪次数: " + ai.numDamage + " 怪物ID " + oid + " 伤害: " + damage + " 技能ID: " + ai.skillId + " 默认上限: " + maxDamagePerHit + lea.toString(true));
                    }
                }
                allDamageNumbers.add(new Pair<>(damage, false));
            }

            AttackPair attackPair = handleMagicAttackPair(ai, lea, oid, allDamageNumbers);
            ai.allDamage.add(attackPair);

            lea.skip(18);

        }
        ai.position = lea.readPos();
        return ai;
    }


    protected AttackPair handleMagicAttackPair(AttackInfo ai, LittleEndianAccessor lea, int oid, List<Pair<Long, Boolean>> allDamageNumbers) {
        // 心魂点数处理
        int ksPsychicObjectId = -1;
        if (ai.skillId == 超能力者.心魂粉碎 || ai.skillId == 超能力者.心魂粉碎2 || ai.skillId == 超能力者.终极_心魂弹) {
            ksPsychicObjectId = lea.readInt();
            lea.skip(9);
        }
        if (ksPsychicObjectId >= 0) {
            return new AttackPair(oid, ksPsychicObjectId, allDamageNumbers);
        } else {
            return new AttackPair(oid, allDamageNumbers);
        }
    }


    protected void parseMagicSkip3(AttackInfo ai, LittleEndianAccessor lea) {
        switch (ai.skillId) {
            case 炎术士.轨道烈焰_LINK:
            case 炎术士.轨道烈焰II_LINK:
            case 炎术士.轨道烈焰III_LINK:
            case 炎术士.轨道烈焰IV_LINK:
            case 地图轮.解放雷之符文: {
                lea.skip(4);
            }
        }

    }


    protected void parseMagicSkip2(AttackInfo ai, LittleEndianAccessor lea) {
        switch (ai.skillId) {
            case 炎术士.轨道烈焰_LINK:
            case 炎术士.轨道烈焰II_LINK:
            case 炎术士.轨道烈焰III_LINK:
            case 炎术士.轨道烈焰IV_LINK:
            case 龙神.狂风之环:
            case 龙神.巨龙迅捷:
            case 龙神.巨龙迅捷_2:
            case 龙神.巨龙迅捷_3:
            case 龙神.雷电之环:
            case 龙神.巨龙俯冲:
            case 龙神.巨龙俯冲_攻击:
            case 龙神.巨龙吐息:
            case 龙神.大地吐息:
            case 龙神.大地之环:
            case 龙神.元素爆破:
                lea.skip(1);
                break;
        }
    }

    private void parseMagicSkip1(AttackInfo ai, LittleEndianAccessor lea) {
        switch (ai.skillId) {
            case 龙神.雷电俯冲:
            case 龙神.雷电俯冲_攻击: {
                lea.skip(4);
            }
        }
    }


    public void rangedAttack(LittleEndianAccessor slea, final MapleClient c, final MapleCharacter chr) {
        /* 解析远程攻击 */
        AttackInfo attack = parseRangedAttack(slea, chr);

        boolean checkAttackResult = JobCheckUtil.checkAttack(chr, c, attack, AttackType.RANGE);
        if (!checkAttackResult) {
            return;
        }


        /* 初始化子弹数量和技能等级信息 */
        int bulletCount = 1;
        int skillLevel = 0;
        MapleStatEffect effect = null;
        Skill skill = null;


        /* 判断攻击的技能是否为普通攻击 */
        if (attack.skillId != 0) {
            CheckSkillEffectInfo checkResult = JobCheckUtil.checkSkillAndEffect(attack, AttackType.RANGE, chr, c, false);
            if (!checkResult.isCheckResult()) {
                return;
            }
            skillLevel = checkResult.getSkillLevel();
            effect = checkResult.getEffect();
            skill = checkResult.getSkill();

            /* 如果当前技能所需的子弹数量大于或等于当前角色的攻击次数，那么最终按技能所需的子弹数量赋值 */
            bulletCount = Math.max(effect.getBulletCount(chr), effect.getAttackCount(chr));
        }

        /* 处理暴击伤害数据 */
        attack = Modify_AttackCrit(attack, chr, 2, effect);

        /* 定义飞镖 和飞镖的外观状态 */
        int projectile = 0;
        int visProjectile = 0;

        bulletCount = handleBulletConsume(c, chr, effect, attack, bulletCount, projectile);
        if (bulletCount == -1) {
            return;
        }
        visProjectile = handleVisProjectile(chr, attack, visProjectile);
        if (visProjectile == -1) {
            return;
        }


        double basedamage;
        int projectileWatk = 0;
        if (projectile != 0) {
            projectileWatk = MapleItemInformationProvider.getInstance().getWatkForProjectile(projectile);
        }
        //System.out.println("飞镖攻击: " + projectileWatk + " 飞镖ID: " + projectile);
        PlayerStats statst = chr.getStat();
        //System.out.println("自身攻击 : " + statst.getCurrentMaxBaseDamage() + " 加成: " + statst.calculateMaxProjDamage(projectileWatk, chr));
        basedamage = statst.getCurrentMaxBaseDamage() + statst.calculateMaxProjDamage(projectileWatk, chr);
        //System.out.println("合计攻击: " + basedamage);

        // x轴伤害加成？
        switch (attack.skillId) {
            case 神射手.爆炸箭:
                if (effect != null) {
                    basedamage *= effect.getX() / 100.0;
                }
                break;
        }


        chr.checkFollow();
        if (chr.isShowPacket()) {
            chr.dropDebugMessage(1, "[远距离攻击] 是否禁止显示给其他玩家: " + SkillFactory.isBlockedSkill(attack.skillId));
        }
        if (!SkillFactory.isBlockedSkill(attack.skillId)) {
            if (!chr.isHidden()) {
                chr.getMap().broadcastMessage(chr, rangedAttack2Broadcast(chr, skillLevel, visProjectile, attack), chr.getTruePosition());
            } else {
                chr.getMap().broadcastGMMessage(chr, rangedAttack2Broadcast(chr, skillLevel, visProjectile, attack), false);
            }
        }
        applyAttack(attack, skill, chr, bulletCount, basedamage, effect, visProjectile);
    }

    protected int handleVisProjectile(MapleCharacter chr, AttackInfo attack, int visProjectile) {
        /* 判断是否需要消耗子弹 */
        boolean noBullet = attack.starSlot == 0 || JobConstants.noBulletJob(chr.getJob());

        /* 如果当前角色是需要消耗子弹或箭矢，且角色特定的状态是否为空 */
        if (!noBullet && chr.getBuffedValue(MapleBuffStat.无形箭弩) == null && !JobConstants.is幻影(chr.getJob())) {
            Item item = chr.getInventory(MapleInventoryType.USE).getItem(attack.starSlot);
            if (item == null) {
                return -1;
            }
            int projectile = item.getItemId();
            if (attack.cashSlot > 0) {
                if (chr.getInventory(MapleInventoryType.CASH).getItem(attack.cashSlot) == null) {
                    chr.dropMessage(5, "检查你的现金投掷道具");
                    return -1;
                }
                visProjectile = chr.getInventory(MapleInventoryType.CASH).getItem(attack.cashSlot).getItemId();
            } else {
                visProjectile = projectile;
            }
        } else if (JobConstants.is机械师(chr.getJob())) {
            visProjectile = 2333000;
        } else if (JobConstants.is火炮手(chr.getJob())) {
            visProjectile = 2333001;
        }
        return visProjectile;
    }

    /**
     * 控制子弹消耗
     * @param c
     * @param chr
     * @param effect
     * @param attack
     * @param bulletCount
     * @param projectile 消耗投掷物品id
     * @return
     */
    protected int handleBulletConsume(MapleClient c, MapleCharacter chr, MapleStatEffect effect, AttackInfo attack, int bulletCount, int projectile) {

        /* 判断角色是否有分身状态 */
        boolean mirror = chr.getBuffedValue(MapleBuffStat.影分身) != null;

        /* 如果角色存在分身状态，那么攻击时子弹的消耗将是平时的2倍 */
        bulletCount *= (mirror ? 2 : 1);

        // Handle bulletcount
        // 没有暗器伤人的标飞，在使用娴熟飞镖术会加标
        int bulletConsume = bulletCount;

        if (chr.getBuffedValue(MapleBuffStat.暗器伤人) == null) {
            Item item = chr.getInventory(MapleInventoryType.USE).getItem(attack.starSlot);
            if (effect != null && effect.getBulletConsume() != 0) {
                bulletConsume = effect.getBulletConsume() * (mirror ? 2 : 1);
            }
            if (chr.getJob() == MapleJob.隐士.getId() && bulletConsume > 0 && item.getQuantity() < MapleItemInformationProvider.getInstance().getSlotMax(projectile)) {
                Skill expert = SkillFactory.getSkill(隐士.娴熟飞镖术);
                if (chr.getTotalSkillLevel(expert) > 0) {
                    MapleStatEffect eff = expert.getEffect(chr.getTotalSkillLevel(expert));
                    if (eff.makeChanceResult()) {
                        item.setQuantity((short) (item.getQuantity() + 1));
                        c.announce(InventoryPacket.modifyInventory(false, Collections.singletonList(new ModifyInventory(1, item))));
                        bulletConsume = 0; //regain a star after using
                        c.announce(InventoryPacket.getInventoryStatus());
                    }
                }
            }
            if (bulletConsume > 0) {
                boolean useItem = true;
                if (chr.getBuffedValue(MapleBuffStat.子弹数量) != null) {
                    int count = chr.getBuffedIntValue(MapleBuffStat.子弹数量) - bulletConsume;
                    if (count >= 0) {
                        chr.setBuffedValue(MapleBuffStat.子弹数量, count); //设置BUFF状态的子弹数量
                        useItem = false;
                    } else {
                        chr.cancelEffectFromBuffStat(MapleBuffStat.子弹数量); //取消当前的BUFF状态
                        bulletConsume += count; //由于这个是 负数 所以要 加
                    }
                }
                //不满足上面的条件 就消耗角色背包的子弹数量
                if (useItem && !MapleInventoryManipulator.removeById(c, MapleInventoryType.USE, projectile, bulletConsume, false, true)) {
                    chr.dropMessage(5, "您的箭/子弹/飞镖不足。");
                    return -1;
                }
            }
        }
        return bulletConsume;
    }


    /*
     * 解析远距离攻击
     */
    public AttackInfo parseRangedAttack(LittleEndianAccessor lea, MapleCharacter chr) {
        //00 01 03 22 0A 51 00 07 00 B8 13 46 93 00 00 00 EB 02 39 2D B5 8C 0C 08 5A 52 B2 04 00 00 00 00 00 00 00 00 00 E0 00 4F 01
        AttackInfo ai = new AttackInfo();
        ai.isRangedAttack = true; //设置该攻击为远距离攻击
        boolean b = lea.readByte() == 1;
        lea.skip(1);
        ai.numAttackedAndDamage = lea.readByte();
        ai.numAttacked = (byte) ((ai.numAttackedAndDamage >>> 4) & 0xF); //攻击怪物数
        ai.numDamage = (byte) (ai.numAttackedAndDamage & 0xF); //攻击次数
        ai.skillId = lea.readInt(); //技能ID
        Skill skill = SkillFactory.getSkill(ai.skillId);
        ai.skllv = lea.readByte();
        lea.skip(6);
        ai.starSlot = lea.readShort();
        lea.skip(4);
        ai.charge = skill != null && skill.isChargeSkill() ? lea.readInt() : 0;
        //神之子这个地方需要多1个
        if (JobConstants.is神之子(chr.getJob()) && ai.skillId >= 100000000) {
            ai.zeroUnk = lea.readByte();
        }
        if (b) {
            lea.skip(4);
        }
        if (SkillConstants.isSkip4Skill(ai.skillId)) {
            lea.skip(4);
        }
        lea.skip(11);
        ai.unk = lea.readByte();
        ai.display = lea.readByte(); //动作
        ai.direction = lea.readByte(); //方向
        if (b) {
            lea.skip(4);
        }
        lea.skip(4); // big bang
        lea.skip(1); // Weapon class

        parseRangedSkip1(ai, lea);


//        if (chr.getCygnusBless()) { //精灵的祝福
//            lea.skip(12); //3个相同的Int
//        }
        ai.speed = lea.readByte(); // 攻击速度
        ai.lastAttackTickCount = lea.readInt(); // Ticks
        lea.skip(4); //0
        lea.readShort();
//        ai.starSlot = lea.readShort(); //消耗飞镖 子弹等等在消耗栏的位置
//        ai.cashSlot = lea.readShort(); //飞镖 子弹等等的商城外形
        ai.AOE = lea.readByte(); // is AOE or not, TT/ Avenger = 41, Showdown = 0
        lea.skip(8);

        long damage;
        int oid;
        List<Pair<Long, Boolean>> allDamageNumbers;
        ai.allDamage = new ArrayList<>();
        boolean isOutput = false;
        long maxDamagePerHit = chr.getMaxDamageOver(ai.skillId);
        for (int i = 0; i < ai.numAttacked; i++) {
            oid = lea.readInt();
            ai.ef = lea.readByte();
            lea.skip(19); //V.112修改 以前19
            allDamageNumbers = new ArrayList<>();
            for (int j = 0; j < ai.numDamage; j++) {
                damage = lea.readLong();
                if (damage > maxDamagePerHit * 1.5 || damage < 0 || oid <= 0) {
                    if (chr.isShowPacket()) {
                        chr.dropDebugMessage(2, "[远距离攻击] 打怪数量: " + ai.numAttacked + " 打怪次数: " + ai.numDamage + " 怪物ID " + oid + " 伤害: " + damage + " 默认上限: " + maxDamagePerHit);
                    }
                    if (ServerConstants.isShowGMMessage()) {
                        WorldBroadcastService.getInstance().broadcastGMMessage(MaplePacketCreator.serverNotice(6, "[GM消息] " + chr.getName() + " ID: " + chr.getId() + " (等级 " + chr.getLevel() + ") 远距离攻击伤害异常。打怪伤害: " + damage + " 地图ID: " + chr.getMapId()));
                    }
                    if (!isOutput) {
                        isOutput = true;
                        log.error("远距离攻击出错封包: 打怪数量: " + ai.numAttacked + " 打怪次数: " + ai.numDamage + " 怪物ID " + oid + " 伤害: " + damage + " 技能ID: " + ai.skillId + " 默认上限: " + maxDamagePerHit + lea.toString(true));
                    }
                }
                allDamageNumbers.add(new Pair<>(damage, false));
            }
            lea.skip(18);
            ai.allDamage.add(new AttackPair(oid, allDamageNumbers));
        }
        ai.position = lea.readPos(); //角色坐标
        if (lea.available() >= 4) {
            ai.skillposition = lea.readPos(); //技能坐标
        }
        return ai;
    }


    private static void parseRangedSkip1(AttackInfo ai, LittleEndianAccessor lea) {
        switch (ai.skillId) {
            case 双弩.飞叶龙卷风:
            case 尖兵.战斗切换_分裂:
            case 80001915:
                lea.skip(12);
                break;
        }
    }


}
